type EnvironmentType = {
  (): void;
  /** 记录哪些key和这个fn有关联，当fn重新执行（收集依赖之前），会将这些keys与fn的关联取消 */
  deps: Set<string | symbol>;
};

/** 临时保存fn（注意1.1.5版本后，fn就变为了environment，1.1.6版本后environment含有所有的） */
export let activeEffect: EnvironmentType | null = null;
/** 保存key和fn的依赖关系：键是key，值是fn的集合 */
export const depsMap: Map<string | symbol, Set<Function>> = new Map();
/** environment的函数栈，让activeEffect始终为effectStack的栈顶 */
const effectStack: EnvironmentType[] = [];

const cleanup = (environment: EnvironmentType) => {
  // 该fn可能被多个key所依赖，因此需要清空所有依赖了这个fn的key中的fn
  const keys = environment.deps;
  keys.forEach((key) => {
    const deps = depsMap.get(key);
    if (deps) {
      deps.delete(environment);
      if (deps.size === 0) {
        depsMap.delete(key);
      }
    }
  });
};

export const effect = (fn: Function) => {
  // 在声明和后续派发更新时，都需要重新收集依赖
  const environment: EnvironmentType = () => {
    // 加入try，防止fn执行出错
    try {
      // 保证activeEffect在运行的一开始一直有值，这样在执行fn时能依赖收集
      activeEffect = environment;
      // 将environment入栈：模拟真实函数栈
      effectStack.push(environment);
      // 收集依赖之前，将之前的依赖全部清除
      cleanup(environment);
      fn();
    } finally {
      // 依赖收集完后要出栈
      effectStack.pop();
      activeEffect = effectStack[effectStack.length - 1] || null;
    }
  };
  environment.deps = new Set(); // 用来记录改environment函数在被哪些key所依赖
  environment();
};
